JavaScript 手写实现

原生行为/方法

instanceof

function myInstanceof(child, parent) {
  if (typeof parent !== 'function') throw `${parent} is not a function`
  const proto = Object.getPrototypeOf(child)
  while (proto) {
    if (proto === parent.prototype) {
      return true
    }
    proto = Object.getPrototypeOf(proto)
  }
  return false
}

改变 instanceof 的行为(了解

class Person {
  static [Symbol.hasInstance](op) {
    return op.__proto__ === this.prototype
  }
  constructor(name) {
    this.name = name
  }
}
const person = new Person('xz')
console.log(person instanceof Person) // true

Object.create

function myObjectCreate(proto) {
  const obj = {}
  Object.setPrototypeOf(obj, proto)
  return obj
}

new

function myNew(Fun, ...args) {
  if (typeof Fun !== 'function') throw `${Fun} is not a function`
  const obj = Object.create(Fun.prototype)
  const res = Fun.apply(obj, args)
  if ((typeof res === 'object' || typeof res === 'function') && res !== null) {
    return res
  }
  return obj
}

bind call apply

Function.prototype.myCall = function (bindThis, ...args) {
  bindThis = bindThis === undefined || bindThis === null ? globalThis : Object(bindThis)
  const fun = this
  const key = Symbol()
  bindThis[key] = fun
  const res = bindThis[key](...args)
  delete bindThis[key]
  return res
}
Function.prototype.myApply = function (bindThis, args = []) {
  bindThis = bindThis === undefined || bindThis === null ? globalThis : Object(bindThis)
  const fun = this
  const key = Symbol()
  bindThis[key] = fun
  const res = bindThis[key](...args)
  delete bindThis[key]
  return res
}
Function.prototype.myBind = function (bindThis, ...args) {
  const fun = this
  const foo = function (...innerArgs) {
    // 处理 new 方式调用
    if (this instanceof fun) {
      return fun.apply(this, args.concat(innerArgs))
    }
    return fun.apply(bindThis, args.concat(innerArgs))
  }
  Object.setPrototypeOf(foo, fun.prototype)
  return foo
}

手动改写隐式转化流程(了解

原理:对象身上有一个 Symbol.toPrimitive 方法,这个方法接受一个 hint 转换参数,默认情况 hint 为

string:先执行 valueOf 在执行 toString (String、模板字符串 ``

number:先执行 toString,再执行 valueOf (Number、正负号+-

default:先执行 valueOf 在执行 toString(隐式转化

const obj = {
  [Symbol.toPrimitive](hint) {
    switch (hint) {
      case "string":
        return "obj-toPrimitive-string";
      case "number":
        return 1;
      case "default":
        return "obj-toPrimitive-default";
    }
  },
};
console.log(`${obj}`); // obj-toPrimitive-string
console.log(+obj);     // 1
console.log(obj + ""); // obj-toPrimitive-default

Array.forEach

Array.prototype.forEach = function (fun, thisArg) {
  if (typeof fun !== "function") throw `${fun} is not a function`;
  const arr = this;
  for (let i = 0; i < arr.length; i++) {
    // 处理空元素
    if (i in arr) {
      fun.call(thisArg, arr[i], i, arr);
    }
  }
};

Array.map

Array.prototype.map = function (fun, thisArg) {
  if (typeof fun !== "function") throw `${fun} is not a function`;
  const arr = this;
  const resArr = new Array(arr.length);
  for (let i = 0; i < arr.length; i++) {
    if (i in arr) {
      resArr[i] = fun.call(thisArg, arr[i], i, arr);
    }
  }
  return resArr;
};

Array.filter

Array.prototype.filter = function (fun, thisArg) {
  if (typeof fun !== "function") throw `${fun} is not a function`;
  const arr = this;
  const resArr = [];
  for (let i = 0; i < arr.length; i++) {
    if (i in arr) {
      const res = fun.call(thisArg, arr[i], i, arr);
      res && resArr.push(res);
    }
  }
  return resArr;
};

Array.reduce

Array.prototype.reduce = function (fun, init) {
  if (typeof fun !== "function") throw `${fun} is not a function`;

  const arr = this;
  let acc = init;
  let cur = 0;

  // 找到第一个非空元素
  if (init === undefined) {
    while (cur < arr.length && !(cur in arr)) cur++;
    if (cur === arr.length) throw "Each element of the array is empty";
    acc = arr[cur++];
  }

  for (; cur < arr.length; cur++) {
    if (cur in arr) {
      // reduce 无this绑定
      acc = fun.call(undefined, acc, arr[cur], cur, arr);
    }
  }
  return acc;
};

工具函数

柯里化

function curry(fun, ...args) {
  return function (...innerArgs) {
    const allArgs = args.concat(innerArgs)
    if (allArgs.length < fun.length) {
      return curry.call(this, fun, ...allArgs)
    } else {
      return fun.apply(this, allArgs)
    }
  }
}

深浅拷贝

function shallowClone(target) {
    if (typeof target !== 'object' || target === null) return target
    const obj = target.constructor()
    for (const key in target) {
        if (Object.hasOwnProperty.call(target, key)) {
            obj[key] = target[key]
        }
    }
    return obj
}
function deepClone(target, cache = new WeakMap()) {
    if ((typeof target !== 'object' && typeof target !== 'function') || target === null) return target
    if (target instanceof Date) return new Date(target)
    if (target instanceof RegExp) return new RegExp(target.source, target.flags)
    if (target instanceof WeakMap) return new WeakMap()
    if (target instanceof WeakSet) return new WeakSet()

    // 这里可以再forin克隆一下函数身上的属性
    if (target instanceof Function) {
        if (target.prototype) {
            return function (...args) {
                return target.apply(this, args)
            }
        } else {
            return (...args) => {
                return target(...args)
            }
        }
    }

    if (cache.has(target)) return cache.get(target)
    const obj = target.constructor()
    cache.set(target, obj)

    if (target instanceof Map) {
        target.forEach((value, key) => {
            obj.set(deepClone(key, cache), deepClone(value, cache))
        });
    }
    if (target instanceof Set) {
        target.forEach((value) => {
            obj.add(deepClone(value, cache))
        });
    }

    for (const key in target) {
        if (Object.hasOwnProperty.call(target, key)) {
            obj[key] = deepClone(target[key], cache)
        }
    }
    return obj
}

防抖节流

const throttle = (fn, delay) => {
    let pre = Date.now()
    return function (...args) {
        const current = Date.now()
        if (current - pre < delay) {
            return
        }
        pre = current
        return fn.apply(this, args)
    }
}
const debounce = (fn, delay) => {
    let timer = null
    return function (...args) {
        if (timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(() => {
            fn.apply(this, args)
        }, delay)
    }
}
function debounce(fun, delay, immediate) {
  let timer = null;
  return function (...args) {
    if (timer) clearTimeout(timer);
    
    // timer 控制当前是否 delay 外的第一次执行
    if (immediate && !timer) {
      fun.apply(this, args);
      timer = setTimeout(() => {
        timer = null;
      }, delay);
      return;
    }
    
    timer = setTimeout(() => {
      timer = null;
      fun.apply(this, args);
    }, delay);
  };
}

loadsh.get

版本号对比

url解析

场景/应用

图片懒加载

function init() {
  const container = document.documentElement || document.body;
  const imgs = container.querySelectorAll("img");

  const GAP = 100;
  let count = 0;

  function lazyLoad() {
    const clientHeight = container.clientHeight;
    const scrollTop = container.scrollTop;
    for (let i = count; i < imgs.length; i++) {
      if (imgs[i].offsetTop <= clientHeight + scrollTop + GAP) {
        imgs[i].src = imgs[i].dataset.src;
        count++;
      }
    }
  }
  container.addEventListener("scroll", throttle(lazyLoad, 100));
}
function init() {
  const container = document.documentElement || document.body;
  const imgs = container.querySelectorAll("img");

  const GAP = 100;
  let count = 0;

  function lazyLoad() {
    const clientHeight = container.clientHeight;
    const scrollTop = container.scrollTop;
    for (let i = count; i < imgs.length; i++) {
      // 与上不同
      if (imgs[i].getBoundingClientRect().top <= clientHeight + GAP) {
        imgs[i].src = imgs[i].dataset.src;
        count++;
      }
    }
  }
  container.addEventListener("scroll", throttle(lazyLoad, 100));
}
function init() {
  const container = document.documentElement || document.body;
  const imgs = container.querySelectorAll("img");
  const GAP = 100;

  const observe = new IntersectionObserver(
    (entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          const img = entry.target;
          img.src = img.dataset.src;
          observe.unobserve(img);
        }
      });
    },
    {
      // 指定容器
      root: container,
      // 预加载;与 margin 规则一致
      rootMargin: `${GAP}px`,
    }
  );
  imgs.forEach((img) => observe.observe(img));
}

LRU

设计模式

观察者

class Subject {
  constructor() {
    this.watchers = new Set();
  }
  add(watcher) {
    this.watchers.add(watcher);
  }
  remove(watcher) {
    this.watchers.delete(watcher);
  }
  notify(...data) {
    this.watchers.forEach((watcher) => {
      watcher.update(...data);
    });
  }
}
class Watcher {
  constructor(update) {
    this.update = update;
  }
}
const watcher1 = new Watcher();
const watcher2 = new Watcher();
const watcher3 = new Watcher();

const sub = new Subject();
sub.add(watcher1);
sub.add(watcher2);
sub.add(watcher3);
sub.notify("");

发布订阅

class EventBus {
  constructor() {
    this.events = new Map();
  }
  on(eventName, callback) {
    if (typeof callback !== "function") {
      throw new TypeError(`${callback} is not a function`);
    }
    if (this.events.has(eventName)) {
      this.events.get(eventName).push(callback);
    } else {
      this.events.set(eventName, [callback]);
    }
    return () => {
      this.off(eventName, callback);
    };
  }
  emit(eventName, ...data) {
    const callbacks = this.events.get(eventName);
    if (!callbacks?.length) {
      return;
    }
    // 避免回调中调用 on/off 修改到原数组,进行浅拷贝
    for (const cb of [...callbacks]) {
      try {
        cb(...data);
      } catch (error) {
        console.error(`事件 ${eventName} 的某个监听执行失败`, error);
      }
    }
  }
  off(eventName, callback) {
    // 全删除
    if (!callback) {
      this.events.delete(eventName);
      return;
    }

    const callbacks = this.events.get(eventName);
    if (!callbacks) {
      return;
    }

    const index = callbacks.indexOf(callback);
    if (index !== -1) {
      callbacks.splice(index, 1);
    }

    if (callbacks.length === 0) {
      this.events.delete(eventName);
    }
  }
  once(eventName, callback) {
    if (typeof callback !== "function") {
      throw new TypeError("EventBus.once: callback 必须是函数");
    }
    
    const wrapper = (...data) => {
      try {
        callback(...data);
      } finally {
        this.off(eventName, wrapper);
      }
    };
    this.on(eventName, wrapper);
  }
  clear() {
    this.events.clear();
  }
}

Promise专项

Promise

Promise.reslove

Promise.resolve = function (param) {
  // 1. 传参是 promise 直接返回
  if (param instanceof Promise) return param
  return new Promise((resolve, reject) => {
    // 2. 如果是 thenable 对象,跟随它的结果
    if (param && param.then && typeof param.then === 'function') {
      return param.then(resolve, reject)
    }
    // 3. 其他直接成功
    else {
      resolve(param)
    }
  })
}

Promise.reject

Promise.reject = function (reason) {
  return new Promise((_, reject) => {
    reject(reason)
  })
}

Promise.all

Promise.all = function (queue) {
  return new Promise((resolve, reject) => {
    // 处理可迭代对象
    queue = Array.from(queue);
    if (queue.length === 0) return resolve([]);

    const res = new Array(queue.length);
    let count = 0;

    for (let i = 0; i < queue.length; i++) {
      // 考虑到 queue[i] 不是 promise
      Promise.resolve(queue[i]).then(
        (result) => {
          res[i] = result;
          count++;
          // 所有promise都成功
          if (count === queue.length) {
            resolve(res);
          }
        },
        (reason) => {
          reject(reason);
        }
      );
    }
  });
};

Promise.race

Promise.race = function (promises) {
  return new Promise((resolve, reject) => {
    // 处理可迭代对象
    promises = Array.from(promises)
    // 空数组永远 pending
    if (promises.length === 0) return

    for (let i = 0; i < promises.length; i++) {
      Promise.resolve(promises[i]).then(
        result => {
          resolve(result)
        },
        reason => {
          reject(reason)
        })
    }
  })
}

Promise.allSettled

Promise.allSettled = function (promises) {
  return new Promise((resolve) => {
    promises = Array.from(promises)
    if (promises.length === 0) return resolve([])

    const res = new Array(promises.length)
    let count = 0

    promises.forEach((promise, index) => {
      // 处理非 promise
      Promise.resolve(promise).then(value => {
        res[index] = { status: 'fulfilled', value }
      }, reason => {
        res[index] = { status: 'rejected', reason }
      }).finally(() => {
        count++
        if (promises.length === count) resolve(res)
      })
    })
  })
}

框架/工程化

vue

react

webpack

vite

vue-router